﻿namespace Core.Controls.CSharpEditor
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Text;
	using System.Threading;
	using System.Windows.Forms;

	using NRefactory = ICSharpCode.NRefactory;
	using Dom = ICSharpCode.SharpDevelop.Dom;
	using ICSharpCode.TextEditor;

	public class CodeEditorControl : TextEditorControl
	{
		#region Initialization

		public CodeEditorControl()
		{
			InitializeComponent();

			if ( IsVisualBasic ) {
				this.SetHighlighting("VBNET");
			} else {
				this.SetHighlighting("C#");
			}
			this.ShowEOLMarkers = false;
			this.ShowInvalidLines = false;
			HostCallbackImplementation.Register(this);
			CodeCompletionKeyHandler.Attach(this);
			ToolTipProvider.Attach(this);

			pcRegistry = new Dom.ProjectContentRegistry(); // Default .NET 2.0 registry

			// Persistence lets SharpDevelop.Dom create a cache file on disk so that
			// future starts are faster.
			// It also caches XML documentation files in an on-disk hash table, thus
			// reducing memory usage.
			pcRegistry.ActivatePersistence(Path.Combine(Path.GetTempPath(),
														"CSharpCodeCompletion"));

			myProjectContent = new Dom.DefaultProjectContent();
			myProjectContent.Language = CurrentLanguageProperties;
		}

		#endregion Initialization

		#region Public Members

		#region Constant/Read-Only Fields

		/// <summary>
		/// Many SharpDevelop.Dom methods take a file name, which is really just a unique identifier
		/// for a file - Dom methods don't try to access code files on disk, so the file does not have
		/// to exist.
		/// SharpDevelop itself uses internal names of the kind "[randomId]/Class1.cs" to support
		/// code-completion in unsaved files.
		/// </summary>
		public const string DummyFileName = "edited.cs";

		#endregion Constant/Read-Only Fields

		#region Static Fields

		public static bool IsVisualBasic = false;

		#endregion Static Fields

		#region Properties
		/// <summary>
		/// Gets or sets the header text.
		/// </summary>
		/// <value>The header text.</value>
		public string HeaderText { get; set; }
		/// <summary>
		/// Gets or sets the footer text.
		/// </summary>
		/// <value>The footer text.</value>
		public string FooterText { get; set; }

		/// <summary>
		/// Gets the full text.
		/// </summary>
		/// <value>The full text.</value>
		public string FullText
		{
			get
			{
				return string.Format("{1}{0}{2}{0}{3}",
					Environment.NewLine, this.HeaderText, this.Text, this.FooterText);
			}
		}

		public Dom.IProjectContent ProjectContent
		{
			get { return myProjectContent; }
		}
		#endregion

		#endregion Public Members

		#region Internal Members

		#region Fields

		internal ImageList imageList1;
		internal Dom.DefaultProjectContent myProjectContent;
		internal Dom.ParseInformation parseInformation = new Dom.ParseInformation();
		internal Dom.ProjectContentRegistry pcRegistry;

		#endregion Fields

		#endregion Internal Members

		#region Protected Members

		#region Methods

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			parserThread = new Thread(ParserThread);
			parserThread.IsBackground = true;
			parserThread.Start();
		}

		#endregion Methods

		#endregion Protected Members

		#region Private Members

		#region Constant/Read-Only Fields

		static readonly Dom.LanguageProperties CurrentLanguageProperties =
			IsVisualBasic ? Dom.LanguageProperties.VBNet : Dom.LanguageProperties.CSharp;

		#endregion Constant/Read-Only Fields

		#region Fields

		private System.ComponentModel.IContainer components;
		private Dom.ICompilationUnit lastCompilationUnit;
		private Thread parserThread;

		#endregion Fields

		#region Methods

		private Dom.ICompilationUnit ConvertCompilationUnit(NRefactory.Ast.CompilationUnit cu)
		{
			Dom.NRefactoryResolver.NRefactoryASTConvertVisitor converter;
			converter = new Dom.NRefactoryResolver.NRefactoryASTConvertVisitor(myProjectContent);
			cu.AcceptVisitor(converter, null);
			return converter.Cu;
		}

		private void InitializeComponent()
		{
			this.components = new System.ComponentModel.Container();
			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CodeEditorControl));
			this.imageList1 = new System.Windows.Forms.ImageList(this.components);
			this.SuspendLayout();
			// 
			// textAreaPanel
			// 
			this.textAreaPanel.Size = new System.Drawing.Size(577, 294);
			// 
			// imageList1
			// 
			this.imageList1.ImageStream = ( (System.Windows.Forms.ImageListStreamer)( resources.GetObject("imageList1.ImageStream") ) );
			this.imageList1.TransparentColor = System.Drawing.Color.Magenta;
			this.imageList1.Images.SetKeyName(0, "class");
			this.imageList1.Images.SetKeyName(1, "method");
			this.imageList1.Images.SetKeyName(2, "property");
			this.imageList1.Images.SetKeyName(3, "field");
			this.imageList1.Images.SetKeyName(4, "enumeration");
			this.imageList1.Images.SetKeyName(5, "namespace");
			this.imageList1.Images.SetKeyName(6, "event");
			// 
			// CodeEditorControl
			// 
			this.Name = "CodeEditorControl";
			this.Size = new System.Drawing.Size(577, 294);
			this.ResumeLayout(false);

		}

		private void ParseStep()
		{
			string code = null;
			if ( this.InvokeRequired ) {
				Invoke(new MethodInvoker(delegate
				{
					code = this.FullText;
				}));
			} else {
				code = this.FullText;
			}
			TextReader textReader = new StringReader(code);
			Dom.ICompilationUnit newCompilationUnit;
			NRefactory.SupportedLanguage supportedLanguage;
			if ( IsVisualBasic )
				supportedLanguage = NRefactory.SupportedLanguage.VBNet;
			else
				supportedLanguage = NRefactory.SupportedLanguage.CSharp;
			using ( NRefactory.IParser p = NRefactory.ParserFactory.CreateParser(supportedLanguage, textReader) ) {
				// we only need to parse types and method definitions, no method bodies
				// so speed up the parser and make it more resistent to syntax
				// errors in methods
				p.ParseMethodBodies = false;

				p.Parse();
				newCompilationUnit = ConvertCompilationUnit(p.CompilationUnit);
			}
			// Remove information from lastCompilationUnit and add information from newCompilationUnit.
			myProjectContent.UpdateCompilationUnit(lastCompilationUnit, newCompilationUnit, DummyFileName);
			lastCompilationUnit = newCompilationUnit;
			parseInformation.SetCompilationUnit(newCompilationUnit);
		}

		private void ParserThread()
		{
			//BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Loading mscorlib..."; }));
			myProjectContent.AddReferencedContent(pcRegistry.Mscorlib);

			// do one initial parser step to enable code-completion while other
			// references are loading
			ParseStep();

			string[] referencedAssemblies = {
				"System", "System.Data", "System.Drawing", "System.Xml", "System.Windows.Forms", "Microsoft.VisualBasic"
			};
			foreach ( string assemblyName in referencedAssemblies ) {
				string assemblyNameCopy = assemblyName; // copy for anonymous method
				//BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Loading " + assemblyNameCopy + "..."; }));
				Dom.IProjectContent referenceProjectContent = pcRegistry.GetProjectContentForReference(assemblyName, assemblyName);
				myProjectContent.AddReferencedContent(referenceProjectContent);
				if ( referenceProjectContent is Dom.ReflectionProjectContent ) {
					( referenceProjectContent as Dom.ReflectionProjectContent ).InitializeReferences();
				}
			}
			if ( IsVisualBasic ) {
				myProjectContent.DefaultImports = new Dom.DefaultUsing(myProjectContent);
				myProjectContent.DefaultImports.Usings.Add("System");
				myProjectContent.DefaultImports.Usings.Add("System.Text");
				myProjectContent.DefaultImports.Usings.Add("Microsoft.VisualBasic");
			}
			//BeginInvoke(new MethodInvoker(delegate { parserThreadLabel.Text = "Ready"; }));

			// Parse the current file every 2 seconds
			while ( !IsDisposed ) {
				ParseStep();

				Thread.Sleep(2000);
			}
		}

		#endregion Methods

		#endregion Private Members
	}
}